package org.wendu.wdoa.common.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.wendu.wdoa.common.CurrentUser;
import org.wendu.wdoa.common.ex.TokenException;
import org.wendu.wdoa.common.token.dao.TokenDao;

import java.util.Date;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class TokenUtils {

    private static String CLAIM_NAME_USERID = "CLAIM_NAME_USERID";
    private static String CLAIM_NAME_USERNAME = "CLAIM_NAME_USERNAME";

    private static int expiredTime = 100;//过期时间，单位s

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext){
        Environment env = applicationContext.getEnvironment();//通过上下文获取环境对象
        String time = env.getProperty("my.expired-time","100");
        try {
            expiredTime = Integer.parseInt(time);
            if(expiredTime < 60) expiredTime = 100;
        } catch (Exception e) {
            expiredTime = 100;
        }
        log.debug("=======令牌过期时间：{}=========", expiredTime);
    }
    //操作字符串的redis工具对象
    private static StringRedisTemplate stringRedisTemplate;
    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate){
        TokenUtils.stringRedisTemplate = stringRedisTemplate;
    }

    //签名生成登录令牌
    public static String Sign(CurrentUser currentUser, String password){

        Algorithm algorithm = Algorithm.HMAC256(password);

        return
                JWT.create()
                        .withClaim(CLAIM_NAME_USERID, currentUser.getUserId())
                        .withClaim(CLAIM_NAME_USERNAME,currentUser.getUserName())
                        .withExpiresAt(new Date(System.currentTimeMillis()+expiredTime*1000))
                        .sign(algorithm);

    }


    //生成登录令牌
    public static String loginSign(CurrentUser currentUser, String password){
        String token = Sign(currentUser,password);
        //登录令牌(第一次生成初识令牌，该令牌会发送客户端，并作为redis存储键)
        //同时作为键和值存储于redis中，过期时间为令牌本省过期时间的两倍
        stringRedisTemplate.opsForValue().set(token,token,expiredTime*2, TimeUnit.SECONDS);
       return token;
    }

    //从令牌中获取当前用户信息
    public static CurrentUser getCurrentUser(String clientToken){

        if(!StringUtils.hasText(clientToken)){
            throw new TokenException("令牌为空！请登录。");
        }

        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = JWT.decode(clientToken);
        } catch (JWTDecodeException e) {
            throw new TokenException("令牌错误！请登录。");
        }

        String userId = decodedJWT.getClaim(CLAIM_NAME_USERID).asString();
        String userName = decodedJWT.getClaim(CLAIM_NAME_USERNAME).asString();

        if(!StringUtils.hasText(userId) || !StringUtils.hasText(userName)){
            throw new TokenException("令牌无用户信息！请登录。");
        }

        return new CurrentUser(userId,userName);
    }


    private static TokenDao tokenDao;

    @Autowired
    public void setTokenDao(TokenDao tokenDao) {
        TokenUtils.tokenDao = tokenDao;
    }

    //验证令牌
    public static CurrentUser verify(String clientToken){

        CurrentUser currentUser = getCurrentUser(clientToken);

        //获取redis中存储的令牌
        String cacheToken = stringRedisTemplate.opsForValue().get(clientToken);
        if (!StringUtils.hasText(cacheToken)){///未能获取redis中存储的令牌
            throw new TokenException("令牌过期！请登录。");
        }
        String userId = currentUser.getUserId();

        //根据账号查询数据库获得密码（密钥）
        String password = tokenDao.findPasswordByUserId(userId);
        if(!StringUtils.hasText(password)){
            throw new TokenException("非法令牌（无密钥）！请登录。");
        }

        Algorithm algorithm = Algorithm.HMAC256(password);

        JWTVerifier verifier = JWT.require(algorithm).build();

        try {
            verifier.verify(cacheToken);//验证redis中存储的令牌
            return currentUser;
        } catch (TokenExpiredException e) {//令牌本身过期，应该延期（刷新）
            String newToken = Sign(currentUser,password);
            stringRedisTemplate.opsForValue().set(clientToken,newToken,expiredTime*2,TimeUnit.SECONDS);
            return currentUser;
        }catch (JWTVerificationException e) {
            throw new TokenException("令牌非法！请登录。");
        }

    }
    //清除redis中的令牌
    public static void removeToken(String clientToken){
        if (StringUtils.hasText(clientToken)){
            stringRedisTemplate.delete(clientToken);
        }
    }

}
